/*
 * $Id: $
 */
package org.rcgwt.client.ui.grid.internal;

import java.util.ArrayList;
import java.util.List;

import org.rcgwt.client.ui.grid.internal.model.IElementModel;
import org.rcgwt.client.ui.grid.internal.model.IElementModelDelta;
import org.rcgwt.client.ui.grid.internal.model.IGridModel;

/**
 * 
 * @author Olivier Oeuillot (latest modification by $Author: $)
 * @version $Revision: $ $Date: $
 */
public abstract class AbstractElementModel implements IElementModel {

    private static final Key buildKey = new Key();

    protected int stateMask;

    protected int modelIndex;

    private String key;

    protected AbstractElementModel() {
    }

    protected void set(int modelIndex, int stateMask) {
        this.modelIndex = modelIndex;
        this.stateMask = stateMask;

        key = null;
    }

    @Override
    public int getModelIndex() {
        assert modelIndex >= 0 : "Invalid model index";

        return modelIndex;
    }

    @Override
    public int getStateMask() {
        return stateMask;
    }

    @Override
    public void updateStateMask(int mask, int bits) {
        int oldStateMask = stateMask;

        stateMask = (stateMask & mask) | bits;

        if (oldStateMask == stateMask) {
            return;
        }

        getModelElementDelta().performStateMaskChanged(oldStateMask, stateMask,
                mask, bits);
    }

    protected IElementModelDelta< ? > getModelElementDelta() {
        IElementModelDelta< ? > parentDelta = ((AbstractElementModel) getParent())
                .getModelElementDelta();

        return parentDelta.getChildDelta(this);
    }

    @Override
    public IElementModelDelta< ? > createDelta() {
        return new DefaultModelElementDelta<IElementModel>(this);
    }

    public final void appendKey(IKey key) {
        hashCode();

        key.add(this.key);
    }

    protected void constructKey(IKey key) {
        key.add("type", getType().ordinal());
        key.add("modelIndex", modelIndex);
    }

    @Override
    public final int hashCode() {
        if (key == null) {
            if (buildKey.isUsed()) {
                Key key2 = new Key();

                constructKey(key2);

                key = key2.build();

            } else {
                buildKey.clear();

                constructKey(buildKey);

                key = buildKey.build();
            }
        }

        return key.hashCode();
    }

    @Override
    public final boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if ((obj instanceof AbstractElementModel) == false) {
            return false;
        }

        AbstractElementModel other = (AbstractElementModel) obj;
        if (hashCode() != other.hashCode()) {
            return false;
        }

        return key.equals(other.key);
    }

    @Override
    public String toString() {
        return "[ModelElement type=" + getType() + " modelIndex=" + modelIndex
                + " state=0x" + Integer.toHexString(stateMask) + "]";
    }

    @Override
    public IGridModel getGridModel() {
        return getParent().getGridModel();
    }

    private static class Key implements IKey {

        private final List<String> keys = new ArrayList<String>(10);

        private boolean used = false;

        private void clear() {
            keys.clear();

            used = true;
        }

        public boolean isUsed() {
            return used;
        }

        public IKey add(String token) {
            keys.add(token);

            return this;
        }

        public IKey add(String token, int value) {
            keys.add(token);
            keys.add(String.valueOf(value));

            return this;
        }

        private String build() {
            StringBuilder sb = new StringBuilder(256);

            for (String key : keys) {
                if (sb.length() > 0) {
                    sb.append(':');
                }
                sb.append(key);
            }

            used = false;
            keys.clear();

            return sb.toString();
        }
    }
}
